# !/usr/bin/env python3
# -*- coding: utf-8 -*-

import math
import random
import xml.etree.ElementTree as EleTree
from util.lib import pretty_xml, get_node_type
from util.topo_adapter import get_topo_set, get_time_slot_len, add_src_node_port_to_dst_set
from util.flow_adapter import get_route_path
from util.solution_adapter import get_plan_set


def _get_flow_set(flow_xml, topo_set):
    """
    从flow_xml中获取流量的flow_id，period，以及当前路由与上一跳路由的对应关系
    """
    route_set = get_route_path(flow_xml, topo_set)

    flow_set = []
    root = EleTree.parse(flow_xml)
    flow_ele_set = root.findall('flow')

    for flow_ele in flow_ele_set:
        # 获得当前流量的flow_id
        flow_id = flow_ele.findtext('stream_id')
        flow_id = int(flow_id)
        # 获取当前的流量类型
        flow_type = flow_ele.findtext('flow_type')
        # 如果当前流量是ST流
        if flow_type == 'ST':
            # 获得当前流量的周期
            period_ele = flow_ele.iter('period')
            period_ele = list(period_ele)
            period = period_ele[0].text
            period = int(period)
        elif flow_type == 'AVB':
            period = 0
        flow = {'flow_id': flow_id, 'period': period, 'flow_type': flow_type}

        for route_ele in route_set:
            if route_ele['flow_id'] == flow_id:
                flow.update(route_ele)
                flow_set.append(flow)

    return flow_set


def _get_fwd_set(plan_set, topo_set):
    fwd_set = []

    for plan_ele in plan_set:
        link_id = plan_ele['link_id']
        macrotick_id = plan_ele['macrotick_id']
        flow_id = plan_ele['flow_id']
        priority = plan_ele['priority']

        # 由于流量在调度周期内可能多次经过某条链路，因此需要判断该流量是否已经加入链路
        flag = True
        for fwd_entry in fwd_set:
            # 如果该链路的转发表项中已经存在该流量的信息
            # 那么该链路就无需再添加该流量
            if link_id == fwd_entry['link_id'] and \
                    flow_id == fwd_entry['flow_id']:
                flag = False
                break
        # 由于plan_xml已经根据流量的发送时间进行了排序，
        # 因此加入fwd_set的表项一定是该流量在调度周期内的第一个报文
        if flag:
            fwd_set.append({'link_id': link_id,
                            'flow_id': flow_id,
                            'macrotick_id': macrotick_id,
                            'priority': priority,
                            'flow_type': 'ST'
                            })
    # forwarding_set排序
    fwd_set.sort(key=lambda x: (x['link_id'],
                                x['flow_id'],
                                x['macrotick_id']))

    add_src_node_port_to_dst_set(fwd_set, topo_set)

    return fwd_set


def _update_period_to_fwd_set(flow_set,
                              fwd_set,
                              time_slot_len):
    """
    将流的周期更新到forwarding_set中，周期要以时间槽为单位
    """
    for entry in fwd_set:
        flow_id = entry['flow_id']
        for flow in flow_set:
            if flow_id == flow['flow_id']:
                period_in_time_slot = flow['period'] / time_slot_len
                period_in_time_slot = math.ceil(period_in_time_slot)
                period_in_time_slot = int(period_in_time_slot)
                entry.update(
                    {'period': period_in_time_slot}
                )
                break
    return


def _update_inject_time_to_fwd_set(flow_set,
                                   fwd_set):
    for flow in flow_set:
        flow_id = flow['flow_id']
        route_length = len(flow['route_link'])
        link_id = flow['route_link'][0]
        for entry in fwd_set:
            if entry['link_id'] == link_id and entry['flow_id'] == flow_id:
                inject_slot = entry['macrotick_id']
                entry.update({
                    'inject_time_slot': inject_slot
                })
                break

        for i in range(1, route_length):
            for entry in fwd_set:
                if entry['link_id'] == flow['route_link'][i] and entry['flow_id'] == flow_id:
                    entry.update({
                        'inject_time_slot': inject_slot
                    })
                    inject_slot = entry['macrotick_id']
                    break
    return


def _generate_fwd_tbl(fwd_set):
    # 将属于同一个节点的转发表项整合到一起
    total_node_set = []
    for dic in fwd_set:
        del dic['link_id']
        del dic['macrotick_id']
        if dic['src_node_id'] not in total_node_set:
            total_node_set.append(dic['src_node_id'])
    fwd_set.sort(key=lambda x: (x['src_node_id'], x['src_port_id'], x['flow_id']))

    fwd_entry_set = []
    for node in total_node_set:
        fwd_entry_set.append(
            {'node_id': node, 'entry_set': []}
        )

    for dic in fwd_set:
        node_id = dic['src_node_id']
        for node in fwd_entry_set:
            if node_id == node['node_id']:
                del dic['src_node_id']
                node['entry_set'].append(dic)

    fwd_entry_set.sort(key=lambda x: x['node_id'])
    return fwd_entry_set


def _update_avb_stream_for_fwd_set(flow_set, fwd_set):
    """
    将flow_set中的AVB流更新到forwarding_set中，
    同时将流类型更新到forwarding_set中
    """
    for flow in flow_set:
        flow_id = flow['flow_id']
        # 如果该流是AVB流
        if flow['flow_type'] == 'AVB':
            # 取出该AVB流的每一跳交换节点，
            # 将其加入forwarding_set。
            # 注意：这里不能加入终端节点
            route = flow['route_node_port'][1:-1]
            for hop in route:
                # 当前节点的node_id和port_id
                src_node_id = hop[0]
                src_port_id = hop[0]
                dic = {
                    'link_id': -1,  # 添加link_id是为了与ST流保持一致
                    'src_node_id': src_node_id,
                    'src_port_id': src_port_id,
                    'flow_id': flow_id,
                    'macrotick_id': -1,
                    'priority': random.choice([3, 4]),
                    'flow_type': flow['flow_type']
                }
                fwd_set.append(dic)
    return


def _remove_unneed_node_for_fwd_set(fwd_set, node_type_set):
    flow_set_res = []
    for flow in fwd_set:
        node_id = flow['src_node_id']
        for node_type in node_type_set:
            if node_type['node_id'] == node_id and node_type['node_type'] == 0:
                flow_set_res.append(flow)
    [print(entry) for entry in flow_set_res]

    return flow_set_res


def _init_fwd_tbl_xml(fwd_set, fwd_tbl_xml):
    # 转发表排序
    # 根据flow_id排序
    for entry in fwd_set:
        entry['entry_set'].sort(key=lambda x: x['flow_id'])
    # print('DEBUG')
    # [print(entry) for entry in fwd_set]
    root = EleTree.Element('forward_tbl')
    for entry in fwd_set:
        node_ele = EleTree.SubElement(root, 'node')
        node_id_ele = EleTree.SubElement(node_ele, 'node_id')
        node_id_ele.text = str(entry['node_id'])
        for flow in entry['entry_set']:
            entry_ele = EleTree.SubElement(node_ele, 'entry')
            flow_id_ele = EleTree.SubElement(entry_ele, 'flow_id')
            flow_id_ele.text = str(flow['flow_id'])
            outport_ele = EleTree.SubElement(entry_ele, 'outport')
            outport = 0x1 << flow['src_port_id']
            outport = hex(outport)
            outport_ele.text = str(outport)
            priority_ele = EleTree.SubElement(entry_ele, 'priority')
            priority_ele.text = str(flow['priority'])
            reserve_ele = EleTree.SubElement(entry_ele, 'reserve')
            reserve_ele.text = '0'
            flow_period_ele = EleTree.SubElement(entry_ele, 'flow_period')
            flow_period_ele.text = str(flow['period'])
            inject_time_slot_ele = EleTree.SubElement(entry_ele, 'inject_time_slot')
            inject_time_slot_ele.text = str(flow['inject_time_slot'])
    tree = EleTree.ElementTree(root)
    pretty_xml(root, '\t', '\n')
    tree.write(fwd_tbl_xml,
               encoding='utf-8',
               xml_declaration=True)
    return


def assemble_fwd_tbl(topo_xml,
                     flow_xml,
                     plan_xml,
                     para_xml,
                     fwd_tbl_xml):

    # 1.从topo_xml中获取topo_set
    topo_set = get_topo_set(topo_xml)
    print("1. topo_set: ")
    [print(entry) for entry in topo_set]
    # 2.获取time_slot_len
    time_slot_len = get_time_slot_len(topo_xml)
    print("2. time_slot_len: ", time_slot_len)
    # 3.获取plan_set
    plan_set = get_plan_set(plan_xml)
    print("3. plan_set: ")
    [print(entry) for entry in plan_set]
    # 4.获取node_type_set
    node_type_set = get_node_type(para_xml)
    print("4. node_type_set: ")
    [print(entry) for entry in node_type_set]

    # 5.从flow_xml中获取flow_set
    flow_set = _get_flow_set(flow_xml, topo_set)
    print("5. flow_set: ")
    [print(entry) for entry in flow_set]

    # 6.获取ST流的fwd_set
    fwd_set = _get_fwd_set(plan_set, topo_set)
    print("6. fwd_set: ")
    [print(entry) for entry in fwd_set]

    # # 7.将flow_set中的AVB流更新到fwd_set中
    # _update_avb_stream_for_fwd_set(flow_set, fwd_set)
    # print("7. update avb stream to fwd_set: ")
    # [print(entry) for entry in fwd_set]

    # 8.将流的周期更新到fwd_set中，周期要以时间槽为单位
    _update_period_to_fwd_set(flow_set, fwd_set, time_slot_len)
    print("8. update period to fwd_set: ")
    [print(entry) for entry in fwd_set]
    # 9.将注入时间槽更新到fwd_set中
    _update_inject_time_to_fwd_set(flow_set, fwd_set)
    print("9. update inject_time to fwd_set: ")
    [print(entry) for entry in fwd_set]
    # 10.剔除fwd_set中多余节点，只保留交换机节点
    fwd_set = _remove_unneed_node_for_fwd_set(fwd_set, node_type_set)
    print("10. remove unneed node to fwd_set: ")
    [print(entry) for entry in fwd_set]

    # 11.初始化转发表结构体FwdTbl，将属于同一个节点的转发表项整合到一起
    fwd_set = _generate_fwd_tbl(fwd_set)

    # 12.生成tbl.xml
    _init_fwd_tbl_xml(fwd_set, fwd_tbl_xml)

    return


def main():
    topo_xml = '../input/topo_feature.xml'
    flow_xml = '../input/flow_feature.xml'
    solution_xml = '../input/solution.xml'
    para_xml = '../input/cfg_parameter.xml'
    fwd_tbl_xml = '../output/tbl/fwd_tbl.xml'
    assemble_fwd_tbl(topo_xml, flow_xml, solution_xml, para_xml, fwd_tbl_xml)
    return


if __name__ == '__main__':
    main()
